#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
Simplified BSD License (BSD)
Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this 
      list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/
#endregion

#region Record of Change
//             							CHANGE HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | DEVELOPER  | DESCRIPTION                                              |
// -------------------------------------------------------------------------------------
// | 09-Nov-09 | HaiNM      | First creation.                                          |
// -------------------------------------------------------------------------------------
//
#endregion

#region Record of Audit
//             							REVIEW HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | REVIEWER   | NOTES                                                    |
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
//
#endregion

#region Using directives
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

using Thn;
using Thn.Drawing;
using Thn.Drawing.Painters;
#endregion

namespace Thn.Interface.Vcl.Themes.BlueSea
{
    /// <summary>
    /// Base class for edit box renderers.
    /// </summary>
#if DEBUG
    public
#else
    public
#endif
 abstract class EditBoxRendererBase : BlueThemeRendererBase, IEditBoxRenderer
    {
        #region Fields
        /// <summary>
        /// Padding for images.
        /// </summary>
        protected int mImagePad = 3;
        /// <summary>
        /// Padding for texts.
        /// </summary>
        protected int mTextPad = 2;
        /// <summary>
        /// Whether a marker should be rendered when both text and null text are not available.
        /// </summary>
        protected bool mDrawMarkerOnNull = true;
        /// <summary>
        /// Vertical size of marker.
        /// </summary>
        protected int mMarkerWidth = 7;
        /// <summary>
        /// Horizontal size of marker.
        /// </summary>
        protected int mMarkerHeight = 7;
        /// <summary>
        /// Thickness of marker.
        /// </summary>
        protected int mMarkerThickness = 1;
        /// <summary>
        /// Padding for marker.
        /// </summary>
        protected int mMarkerPad = 5;
        /// <summary>
        /// Color of marker.
        /// </summary>
        protected Color mMarkerColor = Color.FromArgb(137, 161, 182);
        #endregion

        #region On Render Content
        /// <summary>
        /// Paint the control's content (text, value, data, etc.) onto canvas.
        /// </summary>
        /// <param name="control">The control to be rendered.</param>
        /// <param name="canvas">The canvas on which to paint the control.</param>
        /// <param name="context">The current drawing context.</param>
        protected override void OnRenderContent(IControl control, Graphics canvas, RenderContext context)
        {
            EditBoxRenderContext rc = (EditBoxRenderContext)context;
            EditBox con = (EditBox)control;
            ITheme theme = con.EffectiveTheme;
            string txt = con.Text;
            bool noText = false;
            Image img = null;
            Rectangle r;
            Padding pad = con.Padding;

            #region Render Title
            if (con.TitleLayout != ContentAlignment.MiddleCenter)
            {
                txt = con.Title;

                if ((txt != null) && (txt != ""))
                {
                    //lookup necessary font style
                    IFontStyle font = null;
                    if (con.TitleStyle == TextStyle.Normal)
                    {
                        font = theme.FindFont(StyleNames.EditboxTitle);
                    }
                    else if (con.TitleStyle == TextStyle.Custom)
                    {
                        #region Use font specified in the control
                        font = new SimpleFontStyle(con.Font, con.ForeColor);
                        #endregion
                    }
                    else
                    {
                        string stylename = string.Format("{0}", con.TitleStyle);
                        font = theme.FindFont(stylename);
                    }

                    //render the text
                    if (font != null)
                    {
                        r = rc.TitleRegion;
                        font.DrawString(txt, canvas, r, con.TitleLayout, rc.Multiline);
                    }
                    #region Defensive tracing
                    else
                    {
                        //font is not assigned, forget about drawing
                    }
                    #endregion
                }
                #region Defensive tracing
                else
                {
                    //no need to render an empty text
                }
                #endregion
            }
            #region Defensive tracing
            else
            {
                //do not render text when TitleLayout = MiddleCenter.
            }
            #endregion
            #endregion

            #region Render text
            txt = con.Text;
            //use hint text when main text is empty
            if ((txt == null) || (txt == ""))
            {
                txt = con.TextWhenNull;
                noText = true;
            }
            #region Defensive tracing
            else
            {
                noText = false;
            }
            #endregion

            if ((txt != null) && (txt != ""))
            {
                //lookup necessary font style
                IFontStyle font = null;
                if (rc.TextStyle == TextStyle.Normal)
                {
                    if (!con.Enabled) font = theme.FindFont(StyleNames.EditboxDisabled);
                    else if (noText) font = theme.FindFont(StyleNames.EditboxNull);
                    else if (con.Focused) font = theme.FindFont(StyleNames.EditboxFocused);
                    else if (rc.IsMouseOver) font = theme.FindFont(StyleNames.EditboxOver);
                    else font = theme.FindFont(StyleNames.EditboxNormal);
                }
                else if (rc.TextStyle == TextStyle.Custom)
                {
                    #region Use font specified in the control
                    if (noText) font = theme.FindFont(StyleNames.EditboxNull);
                    else font = new SimpleFontStyle(con.Font, con.ForeColor);
                    #endregion
                }
                else
                {
                    if (noText) font = theme.FindFont(StyleNames.EditboxNull);
                    else
                    {
                        string stylename = string.Format("{0}", rc.TextStyle);
                        font = theme.FindFont(stylename);
                    }
                }

                //render the text
                if (font != null)
                {
                    #region Has font to render
                    r = rc.TextRegion;
                    r = new Rectangle(r.Left + mTextPad, r.Top + mTextPad, r.Width - mTextPad * 2, r.Height - mTextPad * 2);

                    if (con.PasswordChar != 0)
                    {
                        // password edit box, hide character
                        HideCharacter(ref txt, con.PasswordChar);
                    }
                    #region Defensive tracing
                    else
                    {
                        // the editbox is normal, do not hide the character.
                    }
                    #endregion

                    font.DrawString(txt, canvas, r, rc.Alignment, rc.Multiline);
                    #endregion
                }
                #region Defensive tracing
                else
                {

                }
                #endregion
            }
            else
            {
                #region No text to render, draw marker instead
                if (mDrawMarkerOnNull)
                {
                    canvas.TranslateTransform(-0.5f, -0.5f);
                    r = rc.TextRegion;
                    r = new Rectangle(r.Left + mMarkerPad, r.Top + mMarkerPad, r.Width - mMarkerPad * 2, r.Height - mMarkerPad * 2);
                    DrawMarker(canvas, r, mMarkerWidth, mMarkerHeight, mMarkerThickness, rc.Alignment);
                    canvas.TranslateTransform(0.5f, 0.5f);
                }
                #region Defensive tracing
                else
                {
                    //marker is not allowed
                }
                #endregion
                #endregion
            }

            #endregion

            #region Render icon
            if (con.ShowIcon)
            {
                GraphicsState state = canvas.Save();
                canvas.InterpolationMode = InterpolationMode.High;

                img = rc.Icon;
                if (img != null)
                {
                    r = rc.IconRegion;
                    r = new Rectangle(r.Left + mImagePad, r.Top + mImagePad, r.Width - mImagePad * 2, r.Height - mImagePad * 2);
                    canvas.TranslateTransform(0.5f, 0.5f);//fix pixel offset bug
                    canvas.DrawImage(img, r);
                    canvas.TranslateTransform(-0.5f, -0.5f);//fix pixel offset bug
                }
                #region Defensive tracing
                else
                {
                    //icon is not assigned
                }
                #endregion

                canvas.Restore(state);
            }
            #region Defensive tracing
            else
            {
                // not allow show icon, do nothing.
            }
            #endregion
            #endregion

            #region Debug - render debug cues
            //canvas.FillRectangle(Brushes.Black, rc.ControlRegion);
            //canvas.FillRectangle(Brushes.LightPink, rc.TitleRegion);
            //canvas.FillRectangle(Brushes.LightBlue, rc.EditRegion);

            //canvas.DrawRectangle(Pens.Black, rc.ControlRegion);
            //canvas.DrawRectangle(Pens.LightPink, rc.TitleRegion);
            //canvas.DrawRectangle(Pens.LightBlue, rc.EditRegion);

            //canvas.DrawRectangle(Pens.Red, rc.IconRegion);
            //canvas.DrawRectangle(Pens.Green, rc.TextRegion);
            //canvas.DrawRectangle(Pens.Blue, rc.ButtonRegion);
            #endregion            
        }

        #region Hide Character
        void HideCharacter(ref string txt, char p)
        {
            string temp = string.Empty;
            for (int i = 0; i < txt.Length; i++)
            {
                temp += p;
            }

            txt = temp;            
        }
        #endregion

        #endregion

        #region On Render Visual
        /// <summary>
        /// Paint the control's visual stuff (border, background, etc.) onto canvas.
        /// </summary>
        /// <param name="control">The control to be rendered.</param>
        /// <param name="canvas">The canvas on which to paint the control.</param>
        /// <param name="context">The current drawing context.</param>
        protected override void OnRenderVisual(IControl control, Graphics canvas, RenderContext context)
        {
            EditBoxRenderContext rc = (EditBoxRenderContext)context;
            EditBox con = (EditBox)control;
            ITheme theme = con.EffectiveTheme;
            EditBoxFillStyle fillStyle;
            EditBoxBorderStyle borderStyle;
            PainterBase painter = null;
            Rectangle r = rc.EditRegion;
            Pen p = null;
            int y = 0;

            #region Get appropriate fill style & border style

            if (!con.Enabled)
            {
                fillStyle = con.FillStyleDisabled;
                borderStyle = con.BorderStyleDisabled;
            }
            else if (con.Focused)
            {
                fillStyle = con.FillStyleFocused;
                borderStyle = con.BorderStyleFocused;
            }
            else if (rc.IsMouseOver)
            {
                fillStyle = con.FillStyleOver;
                borderStyle = con.BorderStyleOver;
            }
            else
            {
                fillStyle = con.FillStyleNormal;
                borderStyle = con.BorderStyleNormal;
            }

            #endregion

            #region Get appropriate painter
            if (con.Enabled)
            {
                if (con.Focused || con.Inner.Focused)
                {
                    painter = new LightYellowFlat();
                }
                else if (rc.IsMouseOver) painter = new LightBlueGloss();
                else painter = new WhiteInnerShadow();
            }
            else
            {
                painter = new DarkGrayFlat();
            }
            #endregion

            #region Render background
            switch (fillStyle)
            {
                #region Default
                case EditBoxFillStyle.Default:
                    if (con.FillStyleArea == EditBoxStyleArea.EditOnly)
                    {
                        painter.PaintRectangle(canvas, r, false, true);
                    }
                    else
                    {
                        painter.PaintRectangle(canvas, rc.ControlRegion, false, true);
                    }
                    break;
                #endregion

                #region None
                case EditBoxFillStyle.None:
                    break;
                #endregion

                #region Unsupported
                default:
                    throw new NotSupportedException();
                    break;
                #endregion
            }
            #endregion

            #region Render border
            switch (borderStyle)
            {
                #region Default
                case EditBoxBorderStyle.Default:
                    if (con.BorderStyleArea == EditBoxStyleArea.EditOnly)
                    {
                        painter.PaintRectangle(canvas, r, true, false);
                    }
                    else
                    {
                        painter.PaintRectangle(canvas, rc.ControlRegion, true, false);
                    }
                    break;
                #endregion

                #region Underline
                case EditBoxBorderStyle.Underline:
                    y = r.Bottom;
                    p = new Pen(painter.BorderColor);
                    canvas.DrawLine(p, r.Left, y, r.Right, y);
                    p.Dispose();
                    break;
                #endregion

                #region None
                case EditBoxBorderStyle.None:
                    break;
                #endregion

                #region Unsupported
                default:
                    throw new NotSupportedException();
                    break;
                #endregion
            }
            #endregion

            #region Render background & border
            /*
            switch (fillStyle)
            {
                #region Default
                case EditBoxFillStyle.Default:

                    if (borderStyle == EditBoxBorderStyle.Default)
                    {
                        if (con.BorderStyleArea == EditBoxStyleArea.EditOnly)
                        {
                            painter.PaintRectangle(canvas, r, true, true);
                        }
                        else
                        {
                            painter.PaintRectangle(canvas, rc.ControlRegion, true, true);
                        }
                    }
                    else if (borderStyle == EditBoxBorderStyle.Underline)
                    {
                        //painter.PaintRoundedRectangle(canvas, r, false, true, CornerRadius);
                        painter.PaintRectangle(canvas, r, false, true);

                        //y = r.Bottom - 1;
                        y = r.Bottom;
                        p = new Pen(painter.BorderColor);
                        canvas.DrawLine(p, r.Left, y, r.Right, y);
                        p.Dispose();
                    }
                    else
                    {//no border
                        //painter.PaintRoundedRectangle(canvas, r, false, true, CornerRadius);
                        painter.PaintRectangle(canvas, r, false, true);
                    }

                    break;
                #endregion

                #region None
                case EditBoxFillStyle.None:

                    if (borderStyle == EditBoxBorderStyle.Default)
                    {
                        p = new Pen(painter.BorderColor);

                        //paint shape
                        //GraphicsPath path = GraphicUtils.MakeRoundedRectangle(r, true, true, true, true, CornerRadius);
                        //canvas.DrawPath(p, path);
                        canvas.DrawRectangle(p, r);

                        //clean up
                        p.Dispose();
                    }
                    else if (borderStyle == EditBoxBorderStyle.Underline)
                    {
                        //y = r.Bottom - 1;
                        y = r.Bottom;
                        p = new Pen(painter.BorderColor);
                        canvas.DrawLine(p, r.Left, y, r.Right, y);
                        p.Dispose();
                    }
                    else
                    {
                        //don't render
                    }

                    break;

                #endregion

                #region Unknown
                default:
                    throw new NotSupportedException(string.Format("Don't understand FillStyle = {0}", fillStyle));
                    break;
                #endregion
            }

            */
            #endregion

            #region Render visual cues at design mode
            if ((con.DesignMode) &&
                (fillStyle == EditBoxFillStyle.None) &&
                (borderStyle == EditBoxBorderStyle.None))
            {
                //DrawVisualCues(con, canvas, context);
                DrawVisualCues(canvas, rc.EditRegion);
            }
            #region Defensive tracing
            else
            {
                //no need to render visual cues
            }
            #endregion
            #endregion

            #region Render Drop Button
            if (con.mShowDropButton && rc.IsMouseOver)
            {
                Image img;
                Rectangle rect = rc.ButtonRegion;
                rect.Inflate(-4, -4);

                switch (rc.DropButtonState)
                {
                    case ButtonState.Over:
                        img = theme.FindImage(ImageNames.DownOver);
                        break;
                    case ButtonState.Pressed:
                        img = theme.FindImage(ImageNames.DownPressed);
                        break;
                    case ButtonState.Normal:
                    default:
                        img = theme.FindImage(ImageNames.DownNormal);
                        break;
                }
                if (img != null)
                {
                    InterpolationMode backup = canvas.InterpolationMode;
                    canvas.InterpolationMode = InterpolationMode.High;
                    GraphicUtils.DrawImage(canvas, img, rect, false);
                    canvas.InterpolationMode = backup;
                }
                #region Defensive tracing
                else
                {
                    // the image not found, donot render.
                }
                #endregion
            }
            #region Defensive tracing
            else
            {
                // the drop button does not allow to show.
            }
            #endregion
            #endregion
        }
        #endregion

        #region Draw Marker
        /// <summary>
        /// Render a text input marker.
        /// </summary>
        /// <param name="canvas">The graphics to paint on.</param>
        /// <param name="r">The region to calculate position of marker.</param>
        /// <param name="size">Size of marker</param>
        /// <param name="alignment">Alignment of the marker inside the provided region.</param>
        protected virtual void DrawMarker(Graphics canvas, Rectangle r, int width, int height, int thickness, ContentAlignment alignment)
        {
            float ratio = 0.7f;
            int sx = (int)(width * ratio);
            int sy = (int)(height * ratio);

            Point pos = CalculateAlignedPosition(r, width, height, alignment);

            Brush b = new SolidBrush(mMarkerColor);
            canvas.FillRectangle(b, pos.X, pos.Y, sx, thickness); //top
            canvas.FillRectangle(b, pos.X + width - thickness, pos.Y + height - sy, thickness, sy);//right
            canvas.FillRectangle(b, pos.X + width - sx, pos.Y + height - thickness, sx, thickness);//bottom
            canvas.FillRectangle(b, pos.X, pos.Y, thickness, sy);//left

            //clean up
            b.Dispose();
        }
        #endregion

        #region Calculate Aligned Position
        /// <summary>
        /// Calculate position of a rectangle that is aligned inside another rectangle.
        /// </summary>
        /// <param name="r">The main rectangle that provides a boundary to calculate alignment.</param>
        /// <param name="width">Horizontal size of the desired rectangle.</param>
        /// <param name="height">Vertical size of the desired rectangle.</param>
        /// <param name="alignment">The alignment format.</param>
        protected Point CalculateAlignedPosition(Rectangle r, int width, int height, ContentAlignment alignment)
        {
            int x = 0;
            int y = 0;

            switch (alignment)
            {
                #region Bottom Center
                case ContentAlignment.BottomCenter:
                    x = r.Left + r.Width / 2 - width / 2;
                    y = r.Bottom - height;
                    break;
                #endregion

                #region Bottom Left
                case ContentAlignment.BottomLeft:
                    x = r.Left;
                    y = r.Bottom - height;
                    break;
                #endregion

                #region Bottom Right
                case ContentAlignment.BottomRight:
                    x = r.Right - width;
                    y = r.Bottom - height;
                    break;
                #endregion

                #region Middle Center
                case ContentAlignment.MiddleCenter:
                    x = r.Left + r.Width / 2 - width / 2;
                    y = r.Top + r.Height / 2 - height / 2;
                    break;
                #endregion

                #region Middle Left
                case ContentAlignment.MiddleLeft:
                    x = r.Left;
                    y = r.Top + r.Height / 2 - height / 2;
                    break;
                #endregion

                #region Middle Right
                case ContentAlignment.MiddleRight:
                    x = r.Right - width;
                    y = r.Top + r.Height / 2 - height / 2;
                    break;
                #endregion

                #region Top Center
                case ContentAlignment.TopCenter:
                    x = r.Left + r.Width / 2 - width / 2;
                    y = r.Top;
                    break;
                #endregion

                #region Top Left
                case ContentAlignment.TopLeft:
                    x = r.Left;
                    y = r.Top;
                    break;
                #endregion

                #region Top Right
                case ContentAlignment.TopRight:
                    x = r.Right - width;
                    y = r.Top;
                    break;
                #endregion

                #region Unsupported
                default:
                    throw new NotSupportedException();
                    break;
                #endregion
            }

            return new Point(x, y);
        }
        #endregion

        #region IEditBoxRenderer Members

        #region Apply Styles For Inner Text
        /// <summary>
        /// Apply font styles and color for inner textbox.
        /// </summary>
        void IEditBoxRenderer.ApplyStylesForInnerText(EditBox con, NativeWindowsTextBox inner, EditBoxRenderContext rc)
        {
            ITheme theme = con.EffectiveTheme;
            PainterBase painter = null;

            #region Set Textbox Bounds

            #endregion

            #region Apply fill style

            if (con.Focused) painter = new LightYellowFlat();
            else painter = new DarkGrayFlat();

            inner.BackColor = painter.FillColor;

            #endregion

            #region Apply font style

            //lookup necessary font style
            IFontStyle font = null;
            if (rc.TextStyle == TextStyle.Normal)
            {
                font = theme.FindFont(StyleNames.EditboxFocused);
            }
            else if (rc.TextStyle == TextStyle.Custom)
            {
                font = new SimpleFontStyle(con.Font, con.ForeColor);
            }
            else
            {
                string stylename = string.Format("{0}", rc.TextStyle);
                font = theme.FindFont(stylename);
            }

            //render the text
            if (font != null)
            {
                inner.Font = font.Font;
                inner.ForeColor = font.Color;
            }
            #region Defensive tracing
            else
            {
                //font is not assigned, forget about styling
            }
            #endregion

            #endregion
        }
        #endregion

        #endregion

        #region Constructors
        /// <summary>
        /// Default constructor.
        /// </summary>
        public EditBoxRendererBase()
        {
            mDrawVisualCues = false;
        }
        #endregion
    }
}
